Activity: Identify Design Elements
The Activity: Use Case Analysis results in analysis classes, which represent conceptual things which can perform behavior. In design, analysis classes evolve into a number of different kinds of design elements:
These finer distinctions enable us to examine different aspects of the design:
By separating concerns and handling each issue represented by these concepts separately, we simplify the design process and clarify our solution. Identify
Events and Signals
Events are external occurrences which cause some action within the system; they occur at random and potentially independently of one another. The need to respond to events in a timely manner is the primary driver for concurrency requirements in the system. Signals are similar to events, but they arise from internal sources; they are used to communicate asynchronously between different concurrent elements in the system. The determination of the events to which the system must respond and the signals which will be used to communicate between different concurrent parts of the system drive the identification of essential parts of the architecture of the system. To identify events to which the system must respond, look to the use-cases and their associated realizations. For each stimulus sent from an actor to the system, consider how the system becomes aware of the stimulus: is the system polling the user, waiting for a response, or is the receipt triggered by a device or sensor which detects the event? In the case of both events and signals, system interrupts are a typical mechanism by which asynchronously arriving events are communicated in the system. Interrupts provide the ability to tell other parts of the system that something significant has occurred. Interrupts are most useful when they are used to communicate information about a periodic events (things that do not occur on regular intervals) that require immediate response. Polling is more appropriate for cases where the events occur on a natural, periodic cycle or occur at random but more or less continuously, and where the system does not have to respond absolutely immediately. For these kinds of events, the system can buffer the events and respond to them on a periodic interval; use of interrupts would be costly and wasteful. An important aspect of the architecture is to determine which events must be responded to immediately, and how the system will communicate the arrival of those events (e.g. which interrupts will be used). For events which do not require immediate response, the architect must determine which can be responded to periodically and on what intervals the system should poll for the events (different events may require different polling schedules). This may be represented by properties on the event itself representing the priority (e.g. immediate or periodic) and expected frequency (e.g. random or every 2 ms) of the event response. Signals are used to communicate between internal parts of the system, and are typically used to communicate faults or exceptions, although they can be used in any circumstance where asynchronous communication is required. Signals are also typically implemented using interrupts, though polling techniques may be used as well. The importance of identifying signals in the architecture is to establish common rules for how and when signals will be used, and to identify which signals will use which signaling mechanisms (e.g. which specific interrupts or polling messages). Failure to agree on these can be quite disastrous and will lead to highly unpredictable system behavior. Identify Classes, Capsules
and Subsystems
Identify Classes. When the analysis class is simple and already represent a single logical abstraction, it can be directly mapped, 1:1, to a design class. Typically, entity classes survive relatively intact into Design. Since entity classes are typically also persistent, determine whether the design class should be persistent and note it accordingly in the class description. When identifying classes, they should be grouped into Artifact: Design Packages, for organizational and configuration management purposes. See Guidelines: Design Package for more information on how to make packaging decisions. Identify Capsules. Consider the concurrency requirements of the system in the context of the analysis objects identified: is there a need for the system to respond to externally generated events, and if so, which analysis classes are 'active' when the events occur? External events in the Use-Case Model are represented by stimuli coming from actors, interacting with a use case. Look at the corresponding Use-Case Realizations to see which objects interact when an event occurs. Start by grouping the objects together into autonomous sets of collaborating objects - these groupings represent an initial cut at the capsules. The Artifact: Capsule itself represents an independent 'logical' thread of execution. These 'logical' threads of execution are not yet to be confused with or mapped literally to threads of execution in the operating system (though at some point we will map them to operating system threads of execution). Instead, they represent independent conceptual threads of execution in the solution space. Our goal in identifying them at this point in design is to be able to partition the solution into independent units based on natural 'concurrency seams' in the system. Dividing the work in this way makes the problems of dealing with concurrency conceptually simpler, since independent threads of execution can be dealt with separately except to the extent that they share underlying passive classes. In general, a capsule should be considered whenever there is concurrency and concurrency conflicts in the problem domain. A capsule should be used to represent some external concurrent object or concurrent activity within the computer. This gives us the ability to monitor and control concurrent activities. Another natural choice is to use capsules as internal representatives of external physical devices that are connected to a computer since those physical entities are inherently concurrent. These "device driver" capsules serve not only to monitor and control the corresponding physical devices but they also isolate the rest of the system from the specifics of the devices. This means that the rest of the system may not be affected even if the technology behind the devices evolves. Another common place for using active objects is to represent logical concurrent activities. A logical activity represents a conceptual concurrent "object", such as, for example, a financial transaction or a telephone call. Despite the fact that these are not directly manifested as physical entities (although they take place in the physical world), there are often reasons to treat them as such. For instance, we may need to temporarily hold back a particular financial transaction to avoid a concurrency conflict or we may need to abort it due to failures within the system. Since these conceptual objects need to be manipulated as a unit, it is convenient to represent them as objects with interfaces of their own that provide the appropriate functional capabilities. A particular example of this type of conceptual object is an active object controller. Its purpose is to continuously manage one or more other capsules. This normally involves bringing each capsules into the desired operational state, maintaining it in that state in the face of various disruptions such as partial failures, and synchronizing its operation with the operation of other objects. These active object controllers often evolve from Control objects identified during Activity: Use-Case Analysis. Because of their capacity to simply and elegantly resolve concurrency conflicts, capsules are also useful as guardians of shared resources. In this case, one or more resources that are required by multiple concurrent activities are encapsulated within a capsule. By virtue of their built-in mutual exclusion semantics, such guardians automatically protect these resources against concurrency conflicts. Identify Subsystems. When the analysis class is complex, such that it appears to embody behaviors that cannot be the responsibility of a single class acting alone, the analysis class should be mapped to a design subsystem. The design subsystem is used to encapsulate these collaborations in such a way that clients of the subsystem can be completely unaware of the internal design of the subsystem, even as they use the services provided by the subsystem. A design subsystem is logically equivalent to the Artifact: Component in the Artifact: Implementation Model. A subsystem is, effectively, a special kind of package which has only interfaces as public elements. The interfaces provide a layer of encapsulation, allowing the internal design of the subsystem to remain hidden from other model elements. The concept subsystem is used to distinguish it from "ordinary" packages, which are semantic-free containers of model elements; the subsystem represents a particular usage of packages with class-like (behavioral) properties. The decision to create a subsystem from a set of collaborating analysis classes is based largely on whether the collaboration can be or will be developed independently by a separate design team. If the collaborations can be completely contained within a package along with the collaborating classes, a subsystem can provide a stronger form of encapsulation than that provided by a simple package. The contents and collaborations within a subsystem are completely isolated behind one or more interfaces, so that the client of the subsystem is only dependent upon the interface. The designer of the subsystem is then completely isolated from external dependencies; the designer (or design team) is required to specify how the interface is realized, but they are completely free to change the internal subsystem design without affecting external dependencies. In large systems with largely independent teams, this degree of de-coupling combined with the architectural enforcement provided by formal interfaces is a strong argument for the choice of subsystems over simple packages. See Guidelines: Design Subsystem for more information about the factors which affect the choice to use subsystems as design elements. Identify
Subsystem Interfaces
Interfaces define a set of operations which are realized by some classifier. In the Design Model, interfaces are principally used to define the interfaces for subsystems. This is not to say that they cannot be used for classes as well, but for a single class it is usually sufficient to define public operations on the class which, in effect, define its 'interface'. Interfaces are important for subsystems because they allow the separation of the declaration of behavior (the interface) from the realization of behavior (the specific classes within the subsystem which realize the interface). This de-coupling provides us with a way to increase the independence of development teams working on different parts of the system, while retaining precise definitions of the 'contracts' between these different parts. For each subsystem, identify a set of candidate interfaces. Using the grouped collaborations identified in the previous step, identify the responsibility which is 'activated' when the collaboration is initiated. This responsibility is then refined by determining what information must be provided by the 'client' and what information is returned when the collaboration is complete; these sets of information become the prototype input and output parameters and return value for an operation which the subsystem will realize. Define a name for this operation, using the naming conventions defined in the Artifact: Design Guidelines. Repeat this until all operations which will be realized by the subsystem have been defined. Next, group operations together according to their related responsibilities. Smaller groups are preferable to larger groups, since it is more likely that a cohesive set of common responsibilities will exist if there are fewer operations in the group. Keep an eye toward reuse as well - look for similarities that may make it easier to identify related reusable functionality. At the same time, though, don't spend a great deal of time trying to find the ideal grouping of responsibilities; remember, this is just a first-cut grouping and refinement will proceed iteratively throughout the elaboration phase. Look for similarities between interfaces. From the candidate set of interfaces, look for similar names, similar responsibilities, and similar operations. Where the same operations exist in several interfaces, re-factor the interfaces, extracting the common operations into a new interface. Be sure to look at existing interfaces as well, re-using them where possible. The goal is to maintain the cohesiveness of the interfaces while removing redundant operations between interfaces. This will make the interfaces easier to understand and evolve over time. Define interface dependencies. The parameters and return value of each interface operation each have a particular type: they must realize a particular interface, or they must be instances of a simple data type. In cases where the parameters are objects which realizes a particular interface, define dependency associations between the interface and the interfaces on which it depends. Defining the dependencies between interfaces provides useful coupling information to the architect, since interface dependencies define the primary dependencies between elements in the design model. Map the interfaces to subsystems. Once interfaces have been identified, create realization associations between the subsystem and the interfaces it realizes. A realization from the subsystem to an interface indicates that there are one or more elements within the subsystem that realize the operations of the interface. Later, when the subsystem is designed, these subsystem-interface realizations will be refined, with the subsystem designer specifying which specific elements within the subsystem realize the operations of the interface. These refined realizations are visible only to the subsystem designer; from the perspective of the subsystem client, only the subsystem-interface realization is visible. Define the behavior specified by the interfaces. Interfaces often define an implicit state machine for the elements that realize the interface. If the operations on the interface must be invoked in a particular order (e.g. the database connection must be opened before it can be used), a state machine that illustrates the publicly visible (or inferred) states that any design element that realizes the interface must support should be defined. This state machine will aid the user of the interface to better understand the interface, and will aid the designer of elements which realize the interface to provide the correct behavior for their element. Package the interfaces. Interfaces are owned by the Architect; changes to interfaces are always architecturally significant. To manage this, the interfaces should be grouped into one or more packages owned by the architect. If each interface is realized by a single subsystem, the interfaces can be placed within the facade of the subsystem. If the interfaces are realized by more than one subsystem, they should be placed within a separate package owned by the architect. This allows the interfaces to be managed and controlled independently of the subsystems themselves. Identify
Capsule Protocols
Protocols play a role similar to interfaces for event-driven systems: they identify the 'contract' between capsules by defining a matched set of signals which are used to communicate between independent threads of control. While interfaces are primarily used to define synchronous messaging using a function call model of invocation, protocols are primarily used to define asynchronous communication using signal-based messaging. Protocols allow the separation of the declaration of behavior (the set of signals) from the realization of behavior (the elements within the subsystem which realize the interface). This de-coupling provides us with a way to increase the independence of development teams working on different parts of the system, while retaining precise definitions of the 'contracts' between these different parts. For each capsule, identify a set of in and out signals. Using the grouped collaborations identified in earlier steps, identify the responsibility which is 'activated' when the collaboration is initiated. This responsibility is then refined by determining what information must be provided by the 'client' and what information is returned when the collaboration is complete; these sets of information become the prototype input parameters for a signal which the capsule will realize through one of its ports. Define a name for this signal, using the naming conventions defined in the Artifact: Design Guidelines. Repeat this until all signals which will be realized by the capsule have been defined. Next, group signals together according to their related responsibilities. Smaller groups are preferable to larger groups, since it is more likely that a cohesive set of common responsibilities will exist if there are fewer signals in the group. Keep an eye toward reuse as well - look for similarities that may make it easier to identify related reusable functionality. At the same time, though, don't spend a great deal of time trying to find the ideal grouping of responsibilities; remember, this is just a first-cut grouping and refinement will proceed iteratively throughout the elaboration phase. Give the protocol a meaningful name, one that describes the role the protocol plays in capsule collaborations. Look for similarities between protocols. From the candidate set of protocols, look for similar names, similar responsibilities, and similar signals. Where the same signals exist in several protocols, re-factor the protocols, extracting the common signals into a new interface. Be sure to look at existing protocols as well, re-using them where possible. The goal is to maintain the cohesiveness of the protocols while removing redundant signals between protocols. This will make the protocols easier to understand and evolve over time. Map the protocols to capsules. Once protocols have been identified, create ports on the capsules which realize the protocols. The ports of the capsule define its 'interfaces', the behavior that can be requested from the capsule. Later, when the capsule is designed, the behavior specified by the ports will be described by the state machine for the capsule. Define the behavior specified by the protocols. Protocols often define an implicit state machine for the elements that realize the interface. If the input signals on the interface must be received in a particular order (e.g. a 'system-ready' signal must be received before a particular error signal can be received), a state machine that illustrates the publicly visible (or inferred) states that any design element that realizes the protocol must support should be defined. This state machine will aid the user of the capsules which realize the protocol to better understand their behavior, and will aid the designer of capsules to provide the correct behavior for their element. Package the protocols. Protocols are owned by the Architect;
changes to protocols are always architecturally significant. To manage this, the
protocols should be grouped into one or more packages owned by the architect.
This allows the protocols to be managed and controlled independently of the
capsules which realize the protocols. |
Rational Unified
Process |